package compile;

import common.FileUtil;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * 每次的 "编译+运行" 这个过程, 就称为是一个 compile.Task
 */
public class Task {
    /*
    所有临时的java文件的目录
    这里没有用常量，常量的话，这里不能改变了
     */
    private String WORK_DIR = null;
    // 约定代码的类名
    private String CLASS = null;
    // 约定要编译的代码文件名.
    private String CODE = null;
    // 约定存放编译错误信息的文件名
    private String COMPILE_ERROR = null;
    // 约定存放运行时的标准输出的文件名
    private String STDOUT = null;
    // 约定存放运行时的标准错误的文件名
    private String STDERR = null;

    public Task() {
        // 在 Java 中使用 UUID 这个类就能生成一个 UUID 了 目的为了防止多个用户都在提交等等....
        WORK_DIR = "./tmp/" + UUID.randomUUID().toString() + "/";
        CLASS = "Solution";
        CODE = WORK_DIR + "Solution.java";
        COMPILE_ERROR = WORK_DIR + "compileError.txt";
        STDOUT = WORK_DIR + "stdout.txt";
        STDERR = WORK_DIR + "stderr.txt";
    }

    /**
     * 该方法编译和运行一段代码【参数】
     * @param question java 源代码.
     * @return  表示编译运行的结果. 编译出错/运行出错/运行正确.....
     */
    public Answer compileAndRun(Question question) {
        Answer answer = new Answer();
        /*
        0、创建一个临时目录，存放当前这个任务下的代码
         */
        File workDir = new File(WORK_DIR);

        if (!workDir.exists()) {
            workDir.mkdirs();
        }

        //检查当前代码是否安全，主要对代码当中的关键字进行判断，如果存在返回特定的状态码

        if (!checkCodeSafe(question.getCode())) {
            System.out.println("用户提交了不安全的代码!");
            answer.setError(3);
            answer.setReason("您提交的代码可能会危害到服务器, 禁止运行!");
            return answer;
        }

        /*
        1、写入程序到指定java文件
        代码如果安全
        借助工具类FileUtil，把代码，question.getCode()。
        写入到CODE： CODE = WORK_DIR + "Solution.java";这个java文件当中
         */
        FileUtil.writeFile(CODE, question.getCode());

        /*
         * 2、创建子进程开始编译.java程序
         * -d命令是 把编译好的字节码文件存入到这个目录
         */
        String compileCmd = String.format("javac -encoding utf8 %s -d %s", CODE, WORK_DIR);

        System.out.println("编译命令: " + compileCmd);
        //编译的时候，不需要传入标准输出文件，此时只关注COMPILE_ERROR文件   写文件都是按照GBK写的，IDEA默认是UTF8
        CommandUtil.run(compileCmd, null, COMPILE_ERROR);
        //如果文件为空，那么说明编译正确，没有问题。
        String compileError = FileUtil.readFile(COMPILE_ERROR);

        if (!compileError.equals("")) {
            System.out.println("编译出错!");
            answer.setError(1);
            answer.setReason(compileError);
            return answer;
        }

        /*
        3、创建子进程开始运行.java程序
         */
        String runCmd = String.format("java -classpath %s %s", WORK_DIR, CLASS);
        System.out.println("运行命令: " + runCmd);
        //运行的时候，需要传入输出文件
        CommandUtil.run(runCmd, STDOUT, STDERR);
        String runError = FileUtil.readFile(STDERR);
        if (!runError.equals("")) {
            System.out.println("运行出错!");
            answer.setError(2);
            answer.setReason(runError);
            return answer;
        }

        answer.setError(0);
        answer.setStdout(FileUtil.readFile(STDOUT));
        return answer;
    }

    /**
     * 检查用户提交的代码中，是否存在危害程序，该黑名单是可以扩充的
     * @param code
     * @return
     */
    private boolean checkCodeSafe(String code) {
        List<String> blackList = new ArrayList<>();
        // 防止提交的代码运行恶意程序
        blackList.add("Runtime");
        blackList.add("exec");
        // 禁止提交的代码读写文件
        blackList.add("java.io");
        // 禁止提交的代码访问网络
        blackList.add("java.net");

        for (String target : blackList) {
            int pos = code.indexOf(target);
            if (pos >= 0) {
                // 找到任意的恶意代码特征, 返回 false 表示不安全
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Task task = new Task();
        Question question = new Question();
        question.setCode("public class Solution {\n" +
                "    public static void main(String[] args) {\n" +
                "        System.out.println(\"hello world\");\n" +
                "    }\n" +
                "}\n");
        Answer answer = task.compileAndRun(question);
        System.out.println(answer);
    }
}
